package com.binc.testspring.study.nio.netty.task;

import com.binc.testspring.study.nio.netty.simple.My_server_handler;
import com.binc.testspring.util.DateUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * FileName: My_server_Handler2   继承My_server_handler 位的就是服用他的代码,只是对他的   channelReak覆盖重写而已
 * Autho: binC
 * Date:  2022/4/19 15:29
 */
@Slf4j
public class My_server_Handler2 extends My_server_handler {
    /**
     * 读取数据事件(这里我们可以读取客户端发送的消息)
     * <p>
     * 1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
     * 2. Object msg: 就是客户端发送的数据 默认Object
     *
     * @param ctx 上下文   这里能获取到 pipeline   也能获取到channel
     * @param msg 真正的消息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
         //比如我们这里有个非常耗时的业务,   我们希望将任务放入任务队列,等服务端的handler处理完一个之后从队列中自己取下一个继续执行;


        // 原始代码:    一  我们在这里模拟需要非常长时间的 处理
        // Thread.sleep(10*1000);// 10秒
        // ctx.writeAndFlush(Unpooled.copiedBuffer("服务端需要很久时间去处理.....", CharsetUtil.UTF_8));  //执行效果就是:上一行阻塞十秒,然后这里返回一个消息,然后 channelReadComplete()方法那里的消息才能返回;  这里执行完 channelReadComplete方法得不到执行的机会
        // log.info("======= 服务端 go on...");

        /*
        上面的程序,我们的客户端和服务端都会阻塞在这里,等10之后才能继续执行;
            怎么解决这样的问题呢
            方案1: 用户程序自定义普通任务
                //比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交到当前channel 对应的 NIOEventLoop 的 taskQueue中,
            方案2:用户自定义定时任务
            方案3: 非当前reactor线程调用当前channel的各种方法
                    例如在:<B>推送系统</B>的业务场景中,根据用户的标识,找到对应的channel引用,然后调用write类的方法向该用户推送消息,就会进入到这种场景,最终的Write会
                    提交到任务队列中后被异步消费
         */

        //方案1    这个方案与原始代码的区别就是    ChannelRead方法没有执行完,阻塞了,但是不妨碍channelReadComplete方法的执行;
                //比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交到当前channel 对应的 NIOEventLoop 的 taskQueue中,
        ctx.channel().eventLoop().execute(new Runnable() {  //这里将任务提交到 pipeline--channel--eventloop--taskqueue中,这里就是异步的开始,channelread线程中的内容继续往后执行,这个异步任务将人不执行;
            @Override
            public void run() {
                try {

                    Thread.sleep(3 * 1000);// 10秒
                    ctx.writeAndFlush(Unpooled.copiedBuffer("服务端需要很久时间去处理....."+DateUtil.longToStr(System.currentTimeMillis(),"yyyy-MM-dd HH:mm:ss"), CharsetUtil.UTF_8));
                } catch (Exception e) {
                    log.error("发生异常{}",e.getMessage());
                }

            }
        });
        log.info("======= 服务端 go on..."+ DateUtil.longToStr(System.currentTimeMillis(),"yyyy-MM-dd HH:mm:ss"));


        // 方案2   户自定义定时任务
            //这里的任务提交到scheduleTaskQueue中
        ctx.channel().eventLoop().schedule(new Runnable() {  //这里将任务提交到 pipeline--channel--eventloop--Scheduletaskqueue中,这里就是异步的开始,channelread线程中的内容继续往后执行,这个异步任务将人不执行;
            @Override
            public void run() {
                try {

                    // Thread.sleep(3 * 1000);// 10秒
                    ctx.writeAndFlush(Unpooled.copiedBuffer("123123....."+DateUtil.longToStr(System.currentTimeMillis(),"yyyy-MM-dd HH:mm:ss"), CharsetUtil.UTF_8));
                } catch (Exception e) {
                    log.error("发生异常{}",e.getMessage());
                }

            }
        },5, TimeUnit.SECONDS);//三个参数,1任务内容 2延迟多久执行 3 时间单位    5秒后执行任务
        log.info("======= 服务端 go on123123..."+ DateUtil.longToStr(System.currentTimeMillis(),"yyyy-MM-dd HH:mm:ss"));


        // 方案3: 非当前reactor线程调用当前channel的各种方法
        //        例如在:<B>推送系统</B>的业务场景中,根据用户的标识,找到对应的channel引用,然后调用write类的方法向该用户推送消息,就会进入到这种场景,最终的Write会
        //                     提交到任务队列中后被异步消费

        // 思路: 我们在服务端的代码中,有这么一段代码  维护一个集合,我们方案三的重点就是从这个集合中获取channel 不要再用ctx获取channel了,其余代码都是一致的;
        // 时机代码的话我们{} 这个位置写了,并且netty给提供好了这样的集合(channelgroup) 相当的便利了;

        /*
        serverBootstrap.group(bossGroup, workGroup) // 服务端启动对象设置两个线程组       客户端的只需要设置一个
                .channel(NioServerSocketChannel.class) // 使用NioServerSocketChannel作为服务器的通道实现
                .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列等待连接个数
                // .handler(null) // 该handler来对应bossGroup;   childHandler 对应workerGroup
                .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置包hi活动连接状态
                .childHandler(new ChannelInitializer<SocketChannel>() { // 创建一个通道初始化对象(匿名对象)
                    /**
                     * 给pipleline(管道) 设置处理器    其实就是真正的业务处理handler
                     * 匿名内部类  需要重写一个方法

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 在这个位置我们第一次拿到 并为这个channel做了一些操作,   这个时候如果我们维护一个集合将channel放到集合中,在推送系统的时候,我们可以获得到这个channel,然后将业务放到这个channel对应的eventLoop的taskQueue或者scheduleTaskqueuezhong
                        ch.pipeline().addLast(new My_server_Handler2());//这里设置的是自定义的handler
                    }
                }); // 给我们的workerGroup组的 eventLoop 对应的管道设置处理器    即:真正的业务处理器;  这才我们开发中真正最关心的消息的处理逻辑  其他的都是服务器和客户端的设置

*/



    }
}
